package com.juanLL.CMC.models;

import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;

import com.juanLL.CMC.AssetLoader;
import com.juanLL.CMC.Level;
import com.juanLL.CMC.Sound;
import com.juanLL.CMC.models.Citizen.citizenEnum;
import com.juanLL.CMC.models.RecycleBin.recycleBinEnum;
import com.juanLL.CMC.models.Trash.trashEnum;

/**
 * GameWorld class Simulates the game and stores a game state
 * 
 */
public class GameWorld {
	// Timer for task scheduling
	private Timer taskTimer = new Timer(true);
	// delay value for task scheduling
	private long delay;
	private boolean locked = false;
	// random generator
	Random rand = new Random();
	// coordinate holding variables for use during initialization
	float x;
	float y;

	// array lists containing all objects of a type regardless of state
	public ArrayList<Trash> trashList = new ArrayList<Trash>();
	public ArrayList<Citizen> citizenList = new ArrayList<Citizen>();
	public ArrayList<RecycleBin> recycleBinList = new ArrayList<RecycleBin>();
	public Rat rat;

	// bitmap file to use as the background
	public Bitmap background;

	// assetLoader object that fetches resources for this object
	public AssetLoader assetLoader;

	// Rect object to test for collisions
	Rect hitbox;
	// rect object that contains the screen resolution
	Rect screenRes;
	// rect object for the left border of the screen
	Rect screenLeft;
	// rect object for the right border of the screen
	Rect screenRight;
	// rect object for the top border of the screen
	Rect screenTop;
	// rect object for the bottom border of the screen
	Rect screenBottom;
	// rect object that aids in detection of trash lying on the bottom of the
	// screen
	Rect screenFloor;

	// tag constant for debugging id
	public static final String TAG = "GameWorld";
	// constant limits
	public int maxCitizen;
	public int maxTrash;
	public long timeLimit;
	public long citizenMinSpawnDelay;
	public long citizenMaxSpawnDelay;
	public long trashMinThrowDelay;
	public long trashMaxThrowDelay;
	// gravity value for falling trash
	public static final Float GRAVITY = .4f;
	// Model objects
	private Trash t;
	private RecycleBin r;
	private Citizen c;
	// score counters
	public int score = 0;
	public int combo = 0;
	boolean comboPrev = false;
	private boolean spawnCitizen;
	private boolean spawnTrash;
	private boolean hasTouchedTrash = false;
	private Integer touchedTrash = null;
	private Sound sound;
	public int winScore;
	public int levelID;
	public boolean pause = false;
	public int pausex;
	public int pausey;
	public Bitmap pauseBitmap;
	public Rect pauseHitbox;
	public boolean transition = false;
	public long timeT;
	public long transitionTimef;
	public long transitionDuration = 5000;
	public Bitmap splashScreen;
	public int levelid = 0;
	public boolean end = false;

	/**
	 * GameWorld constructor
	 * 
	 * @param context
	 *            the context from which it was called
	 * @param screenRes
	 *            the screen resolution in a Rect object
	 */
	public GameWorld(Context context, Rect screenRes) {
		this.assetLoader = new AssetLoader(context);
		this.screenRes = screenRes;
		loadLevel(Level.level0,
				assetLoader.loadBitmap(AssetLoader.PATH_BACKGROUND_CITY1),
				Bitmap.createScaledBitmap(
						assetLoader.loadBitmap(AssetLoader.PATH_SPLASH_1),
						screenRes.right, screenRes.bottom, false));
		transition = true;
		// set border rects
		setBorderRects();
		// initialize the world background
		initWorldBackground();
		// initialize all the models
		initWorldModels();
		// schedule spawn timer task
		setSpawnTimer();
		// schedule throw timer task
		setThrowTimer();
		sound = new Sound(assetLoader);
		timeT = System.currentTimeMillis();

	}

	/**
	 * setBorderRects method, takes the screenRes Rect of the class and creates
	 * rect objects positioned at each border of the screen for use in collision
	 * detection
	 */
	public void setBorderRects() {
		screenLeft = new Rect(screenRes.left, screenRes.top,
				screenRes.left + 1, screenRes.bottom);
		screenRight = new Rect(screenRes.right - 1, screenRes.top,
				screenRes.right, screenRes.bottom);
		screenTop = new Rect(screenRes.left, screenRes.top, screenRes.right,
				screenRes.top + 1);
		screenBottom = new Rect(screenRes.left, screenRes.bottom - 2,
				screenRes.right, screenRes.bottom);
		screenFloor = new Rect(screenRes.left, screenRes.bottom - 10,
				screenRes.right, screenRes.bottom);

	}

	public void loadLevel(Level level, Bitmap background, Bitmap splashScreen) {
		this.levelID = level.levelID;
		this.winScore = level.winScore;
		this.maxCitizen = level.maxCitizen;
		this.maxTrash = level.maxTrash;
		this.timeLimit = level.timeLimit;
		this.citizenMinSpawnDelay = level.citizenMinSpawnDelay;
		this.citizenMaxSpawnDelay = level.citizenMaxSpawnDelay;
		this.trashMinThrowDelay = level.trashMinThrowDelay;
		this.trashMaxThrowDelay = level.trashMaxThrowDelay;
		this.background = background;
		this.splashScreen = splashScreen;
	}

	/**
	 * initWorldBackground method loads the background bitmap
	 */
	public void initWorldBackground() {

	}

	/**
	 * initWorldModels method creates all necessary model instances Trash,
	 * citizens and recycle bins
	 * 
	 */
	public void initWorldModels() {
		Bitmap bitmap;
		Trash trash;
		Citizen citizen;
		RecycleBin recycleBin;

		while (trashList.size() < maxTrash) {
			for (trashEnum t : trashEnum.values()) {
				switch (t.type) {
				case Model.TYPE_GLASS:
					bitmap = assetLoader.loadBitmap(t.path);
					trash = new Trash(t.type, bitmap);
					trashList.add(trash);
					break;
				case Model.TYPE_ORGANIC:
					bitmap = assetLoader.loadBitmap(t.path);
					trash = new Trash(t.type, bitmap);
					trashList.add(trash);
					break;
				case Model.TYPE_PLASTIC:
					bitmap = assetLoader.loadBitmap(t.path);
					trash = new Trash(t.type, bitmap);
					trashList.add(trash);
					break;
				}
			}
		}

		for (recycleBinEnum r : recycleBinEnum.values()) {
			switch (r.type) {
			case Model.TYPE_GLASS:
				bitmap = assetLoader.loadBitmap(r.path);
				x = (float) ((screenRes.right / 2) - (bitmap.getWidth() / 2));
				y = (float) (screenRes.bottom - bitmap.getHeight());
				recycleBin = new RecycleBin(r.type, bitmap, screenRes, x, y);
				recycleBinList.add(recycleBin);
				break;
			case Model.TYPE_ORGANIC:
				bitmap = assetLoader.loadBitmap(r.path);
				x = (float) ((screenRes.right / 2) - (bitmap.getWidth() / 2)
						- bitmap.getWidth() - 60);
				y = (float) (screenRes.bottom - bitmap.getHeight());
				recycleBin = new RecycleBin(r.type, bitmap, screenRes, x, y);
				recycleBinList.add(recycleBin);
				break;
			case Model.TYPE_PLASTIC:
				bitmap = assetLoader.loadBitmap(r.path);
				x = (float) ((screenRes.right / 2) - (bitmap.getWidth() / 2)
						+ bitmap.getWidth() + 60);
				y = (float) (screenRes.bottom - bitmap.getHeight());
				recycleBin = new RecycleBin(r.type, bitmap, screenRes, x, y);
				recycleBinList.add(recycleBin);
				break;
			}

			// initialize rat object
			bitmap = assetLoader.loadBitmap(AssetLoader.RAT_PATH);
			rat = new Rat(screenRes, bitmap);
		}

		while (citizenList.size() < maxCitizen) {
			for (citizenEnum c : citizenEnum.values()) {
				switch (c.type) {
				case Model.TYPE_CITIZEN:
					bitmap = assetLoader.loadBitmap(c.path);
					citizen = new Citizen(c.type, bitmap, screenRes);
					citizenList.add(citizen);
					break;
				}
			}
		}

		pauseBitmap = assetLoader.loadBitmap(AssetLoader.PATH_PAUSE_BUTTON);
		pausex = screenRes.right - pauseBitmap.getWidth();
		pausey = screenRes.top;
		pauseHitbox = new Rect(pausex, pausey, pausex + pauseBitmap.getWidth(),
				pausey + pauseBitmap.getHeight());

	}

	/**
	 * setSpawnTimer method schedules a new spawnTask
	 */
	public void setSpawnTimer() {
		// generate a random delay value within the limits
		delay = (long) (rand.nextFloat()
				* (citizenMaxSpawnDelay - citizenMinSpawnDelay) + citizenMinSpawnDelay);
		// create a new spawnTask object
		SpawnTask spawnTask = new SpawnTask();
		// schedule the spawnTask with the delay
		taskTimer.schedule(spawnTask, delay);
	}

	/**
	 * setThrowTimer method schedules a new throwTask
	 */
	public void setThrowTimer() {
		// generate a random delay value within the limits
		delay = (long) (rand.nextFloat()
				* (trashMaxThrowDelay - trashMinThrowDelay) + trashMinThrowDelay);
		// create a new throwTask object
		ThrowTask throwTask = new ThrowTask();
		// schedule the throwTask with the delay
		taskTimer.schedule(throwTask, delay);
	}

	/**
	 * update method updates the position of the objects in the world
	 */
	public void update() {
		if (!pause && !transition) {
			// update trash
			if (!trashList.isEmpty()) {
				for (int i = 0; i < trashList.size(); i++) {
					t = trashList.get(i);
					if (!t.isInitial) {
						t.update();
						hitbox = t.getHitbox();
						if ((hitbox.left <= screenRes.left && t.xSpeed < 0)
								|| (hitbox.right >= screenRes.right && t.xSpeed > 0)) {
							t.borderCollisionX();
						}
						if ((hitbox.top <= screenRes.top && t.ySpeed < 0)
								|| (hitbox.bottom >= screenRes.bottom && t.ySpeed > 0)
								&& !t.isStolen) {
							t.borderCollisionY();
						}
						if (hitbox.bottom == screenRes.bottom) {
							t.floorFriction();
						}
					}
				}

				if (spawnTrash) {
					int scan = 0;
					do {
						t = trashList.get(rand.nextInt(trashList.size()));
						scan++;
					} while (!t.isInitial || scan <= 30);

					if (!citizenList.isEmpty()) {
						for (int j = 0; j < citizenList.size(); j++) {
							c = citizenList.get(j);
							if (!c.isInitial && c.x >= 10) {
								c.launchTrash(t);
								spawnTrash = false;
							}
						}
					}
				}

			}

			// update citizens
			if (!citizenList.isEmpty()) {
				for (int i = 0; i < citizenList.size(); i++) {
					c = citizenList.get(i);
					if (!c.isInitial) {
						c.update();
					} else {
						if (spawnCitizen) {
							c.spawn();
							spawnCitizen = false;
						}
					}
				}

			}
			// update recycleBin
			for (int i = 0; i < recycleBinList.size(); i++) {
				r = recycleBinList.get(i);
				for (int j = 0; j < trashList.size(); j++) {
					t = trashList.get(j);
					if (!t.isInitial) {
						if (Rect.intersects(t.getHitboxSmaller(), r.getHitbox())
								&& !t.isStolen && t.ySpeed >= 0) {
							if (t.type == r.type) {
								sound.playSound(sound.goodCatch, 0);
								if (comboPrev)
									combo++;
								score += combo;
								comboPrev = true;
							} else {
								sound.playSound(sound.badCatch, 0);
								if (score > 5)
									score -= 5;
								comboPrev = false;
								combo = 0;

							}
							t.catched();
							t.setState(Trash.STATE_INITIAL);

						}
					}

				}

			}

			// update rat
			if (!trashList.isEmpty() && !rat.isSpawned) {
				for (int i = 0; i < trashList.size(); i++) {
					t = trashList.get(i);
					if (!t.isInitial) {
						// if there is trash in the bottom of the screen
						if (Rect.intersects(screenFloor, t.getHitbox())) {
							rat.goForTrash();
						}
					}

				}
			}
			if (!trashList.isEmpty() && rat.isSpawned) {
				rat.update();
				if (!rat.hasTrash) {
					for (int i = 0; i < trashList.size(); i++) {
						t = trashList.get(i);
						if (!t.isInitial) {
							// if the rat intersects a trashObject
							if (Rect.intersects(rat.getHitbox(), t.getHitbox())
									&& t.xSpeed == 0) {
								t.setState(Trash.STATE_STOLEN);
								rat.grabTrash(t);
							}
						}

					}
				}
			}

			if (score >= winScore) {
				if (levelid == 2) {
					pause = true;
					end = true;
					splashScreen = Bitmap.createScaledBitmap(
							assetLoader.loadBitmap(AssetLoader.PATH_SPLASH_3),
							screenRes.right, screenRes.bottom, false);
				} else {
					score = winScore;
					transition = true;
					timeT = System.currentTimeMillis();
					reset();
				}
			}
		}

		if (transition) {
			transitionTimef = System.currentTimeMillis() - timeT;
			if (background != null) {
				background.recycle();
				splashScreen.recycle();
				System.gc();
			}
			switch (levelid) {
			case 0:
				loadLevel(Level.level0,
						assetLoader
								.loadBitmap(AssetLoader.PATH_BACKGROUND_CITY1),
						Bitmap.createScaledBitmap(assetLoader
								.loadBitmap(AssetLoader.PATH_SPLASH_1),
								screenRes.right, screenRes.bottom, false));
				break;
			case 1:
				loadLevel(Level.level1,
						assetLoader
								.loadBitmap(AssetLoader.PATH_BACKGROUND_CITY2),
						Bitmap.createScaledBitmap(assetLoader
								.loadBitmap(AssetLoader.PATH_SPLASH_2),
								screenRes.right, screenRes.bottom, false));
				break;
			}

			if (transitionTimef >= transitionDuration) {
				transition = false;
				levelid++;
			}

		}
	}

	public void reset() {
		score = 0;
		combo = 0;

		for (int i = 0; i < trashList.size(); i++) {
			t = trashList.get(i);
			t.setState(Trash.STATE_INITIAL);
			t.x = screenRes.left - t.bitmap.getWidth();
			t.y = screenRes.top - t.bitmap.getHeight();
			t.xSpeed = t.ySpeed = 0;
		}

		for (int i = 0; i < citizenList.size(); i++) {
			c = citizenList.get(i);
			c.setState(Citizen.STATE_INITIAL);
			c.x = screenRes.left - c.bitmap.getWidth();
			c.xSpeed = 0;
		}

		rat.die();
	}

	/**
	 * doTouch method, called from the InputController when a user first presses
	 * the screen and initiates a MotionEvent
	 * 
	 * @param x
	 *            the x coordinate where the user pressed the screen
	 * @param y
	 *            the y coordinate where the user pressed the screen
	 */
	public void doTouchDown(Float x, Float y) {
		if (!trashList.isEmpty() && !pause && !transition) {
			for (int i = 0; i < trashList.size(); i++) {
				t = trashList.get(i);
				if (!t.isInitial && !t.isStolen && !hasTouchedTrash) {
					// if the user touched the Trash object
					if (t.getHitboxBigger()
							.contains(x.intValue(), y.intValue())) {
						t.touched(x, y);
						touchedTrash = trashList.indexOf(t);
						hasTouchedTrash = true;
					}
				}

			}
		}

		if (rat.isSpawned && !pause) {
			if (rat.getHitbox().contains(x.intValue(), y.intValue())) {
				sound.playSound(sound.ratSqueal, 0);
				rat.hit();
			}
		}

		if (pauseHitbox.contains(x.intValue(), y.intValue())) {
			if (pause) {
				pause = false;
			} else {
				pause = true;
			}
		}
	}

	/**
	 * doTouchMove method, called when the user moves the finger through the
	 * screen after initially pressing it
	 * 
	 * @param x
	 *            the x position where the user's finger is after the move
	 * @param y
	 *            the y position where the user's finger is after the move
	 */
	public void doTouchMove(Float x, Float y) {
		if (touchedTrash != null && !pause && !transition) {
			t = trashList.get(touchedTrash);
			t.touchedAndMove(x, y);
		}

	}

	/**
	 * doTouchUp method, called when the user lifts his finger after having
	 * touched Trash and moved the finger away to launch it.
	 * 
	 * @param x
	 *            the x position where the user lift his finger
	 * @param y
	 *            the y position where the user lift his finger
	 */
	public void doTouchUp(Float x, Float y) {
		if (touchedTrash != null) {
			t = trashList.get(touchedTrash);
			t.launched(x, y);
			touchedTrash = null;
			hasTouchedTrash = false;
		}
	}

	/**
	 * 
	 * ThrowTask class defines a custom timertask to be run after being
	 * scheduled by a timer
	 * 
	 */
	class ThrowTask extends TimerTask {
		Citizen citizen;
		Trash trash;

		/**
		 * run method called at the scheduled time
		 */
		@Override
		public void run() {
			spawnTrash = true;
			// reschedule the task
			setThrowTimer();
		}
	}

	/**
	 * 
	 * SpawnTask class defines a custom timertask to be run after being
	 * scheduled by a timer
	 * 
	 */
	class SpawnTask extends TimerTask {

		/**
		 * run method called at the scheduled time
		 */
		@Override
		public void run() {
			spawnCitizen = true;
			// reschedule the task
			setSpawnTimer();
		}

	}

}
